/*
 * tiny.c - 
 * A simple, concurrent HTTP/1.0 Web server that uses the GET/POST/HEAD
 * method to serve static and dynamic content.
 *
 * Copyright (C) 2009 Xu Zhi <xuzhi86@gmail.com>
 *
 * This program is free software. Permission is granted to redistribute and
 * modify this program under the terms of the GNU Gerneral Public License,
 * avaiable at http://www.gnu.org/ .
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <wait.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/select.h>
#include <sys/time.h>

#include "open.h"
#include "rio.h"
#include "init.h"
#include "log.h"
#include "http.h"
#include "child.h"

extern child    *cptr;          /* array of child structures */
extern int      port;           /* connection port */
extern int      child_init;     /* initial child number */
extern int      child_interval; /* interval for check child state */

void sig_chld(int sig)
{
    while (waitpid(-1, NULL, WNOHANG) > 0);
}

void sig_int(int sig)
{
    log_error("terminated by SIGINT!");
    child_killall();
    exit(1);
}

int main(int argc, char const* argv[])
{
    int                 listenfd, connfd, nready, maxfd, i, n;
    socklen_t           clientlen;
    child               *cp, *next;
    fd_set              rset, masterset;
    struct timeval      tv;
    struct sockaddr_in  clientaddr;

    /* read the configure file */
    readconf();

    /* open listen socket fd */
    listenfd = open_listenfd(port);
    clientlen = sizeof(clientaddr);

    /* turn ourself into a daemon */
    daemonize(listenfd);

    /* make sure only one copy of the daemon is running */
    if (already_running()) {
        log_error("already running!");
        exit(1);
    }
    log_info("tiny %d daemonized success!", getpid());

    FD_ZERO(&masterset);
    FD_SET(listenfd, &masterset);
    maxfd = listenfd;
    
    signal(SIGINT, sig_int);
    signal(SIGCHLD, sig_chld);

    /* create children to work */
    for (i = 0; i < child_init; i++)
        child_make(listenfd, &masterset, &maxfd);

    log_info("begin web serve loop...");
    while (1) {
        rset = masterset;
        tv.tv_sec = child_interval;
        tv.tv_usec = 0;
        while ((nready = select(maxfd+1, &rset, NULL, NULL, &tv)) < 0);
    
        /* new client connection */
        if (FD_ISSET(listenfd, &rset)) {
            connfd = accept(listenfd, (struct sockaddr *)&clientaddr, 
                &clientlen);
            if (connfd < 0) {
                log_error("accept error: %s", strerror(errno));
                continue;
            }

            child_schedule(connfd, listenfd, &masterset, &maxfd); 
            if (--nready == 0)
                continue;
        }

        /* check all child processes */
        cp = cptr;
        while (cp != NULL) {
            next = cp->child_next;

            /* check child stat - connection counts */
            if (FD_ISSET(cp->child_pipefd, &rset)) {
                if ((n = rio_readn(cp->child_pipefd, &cp->child_stat, 
                    sizeof(int))) <= 0) {
                    log_error("read child_stat from %d error %d: %s", 
                        cp->child_pid, n, strerror(errno));
                    child_delete(cp, &masterset, 1);
                    cp = NULL;
                }
            }   

            if (cp != NULL && cp->child_stat == 0) {
                if (cp->child_time != 0) {
                    if (time(NULL) - cp->child_time >= child_interval) {
                        log_info("child %d have no connection in %d secs", 
                            cp->child_pid, child_interval);
                        cp->child_time = time(NULL);
                        child_delete(cp, &masterset, 0);
                        cp = NULL;
                    }
                }
                else
                    cp->child_time = time(NULL);
            }

            cp = next;
        }
    }

    return 0;
}
